/*
    Code : Dzlua
    Email: 505544956@qq.com
    Time : 2017/05/28
*/
#include "database.h"

#include <codecvt>

#include <sqlite/sqlite3.h>

namespace dzlua {

class SQLite : public DataBase {
public:
    class SQLiteCommand : public Command {
    public:
        SQLiteCommand(SQLite* sqlite);
        SQLiteCommand(SQLite* sqlite, const std::string& sql);
        SQLiteCommand(SQLite* sqlite, const std::wstring& sql);
        ~SQLiteCommand();
    public:
        bool SetCommand(const std::string& sql);
        bool SetCommand(const std::wstring& sql);
        bool BindParam(int idx, const std::string& value);
        bool BindParam(int idx, const std::wstring& value);
        bool BindParam(int idx, const int value);
        bool BindParam(int idx, const long value);
        bool BindParam(int idx, const double value);
        bool BindParam(int idx, const DataBlob* blob);
        bool Excute();
        void Clear();
    protected:
        SQLite *m_sqlite;
        sqlite3_stmt *m_stmt;
    };

    class SQLiteDataReader : public DataReader {
    public:
        SQLiteDataReader(sqlite3_stmt *stmt);
        ~SQLiteDataReader();
    public:
        bool Read();
        void Close();
        int Count();
        std::string NameA(int col);
        std::wstring NameW(int col);
        int Type(int col);
        std::string GetAString(int col);
        std::wstring GetWString(int col);
        int GetInt(int col);
        long GetInt64(int col);
        double GetFloat(int col);
        DataBlob GetBlob(int col);
#if defined(UNICODE) || defined(_UNICODE)
		std::wstring Name(int col);
		std::wstring GetString(int col);
#else
		std::string Name(int col);
		std::string GetString(int col);
#endif
    protected:
        sqlite3_stmt *m_stmt;
    };
public:
    friend class SQLiteCommand;
    SQLite();
    ~SQLite();
public:
    std::unique_ptr<Command> NewCommand();
    bool Open(const std::string& file);
    bool Open(const std::wstring& file);
    void Close();
    bool ExcuteNonQuery(const std::string& sql);
    bool ExcuteNonQuery(const std::wstring& sql);
    bool ExcuteNonQuery(Command* pcmd);
    std::unique_ptr<DataReader> ExcuteQuery(const std::string& sql);
    std::unique_ptr<DataReader> ExcuteQuery(const std::wstring& sql);
    bool ExcuteQuery(const std::string& sql, QueryCallback callback);
    bool ExcuteQuery(const std::wstring& sql, QueryCallback callback);
    bool BeginTransaction();
    bool CommitTransaction();
    bool RollbackTransaction();
    std::string GetLastErrMsgA();
    std::wstring GetLastErrMsgW();
#if defined(UNICODE) || defined(_UNICODE)
	std::wstring GetLastErrMsg();
#else
	std::string GetLastErrMsg();
#endif
protected:
    sqlite3 *m_db;
};

//------------------------------------//
namespace {
    std::string WcharToUtf8(const std::wstring& wstr) {
        std::string str;
        std::wstring_convert<std::codecvt_utf8<wchar_t>> conv;
        str = conv.to_bytes(wstr);
        return std::move(str);
    }
} //end namespace

//------------------------------------//
SQLite::SQLite() : m_db(nullptr) {}

SQLite::~SQLite() {
    this->Close();
}

std::unique_ptr<DataBase::Command> SQLite::NewCommand() {
    return std::unique_ptr<DataBase::Command>(new SQLiteCommand(this));
}

bool SQLite::Open(const std::string& file) {
    if (SQLITE_OK != sqlite3_open(file.c_str(), &m_db))
        return false;
    return true;
}

bool SQLite::Open(const std::wstring& file) {
    if (SQLITE_OK != sqlite3_open16(file.c_str(), &m_db))
        return false;
    return true;
}

void SQLite::Close() {
    if (m_db) {
        sqlite3_close(m_db);  
        m_db = nullptr;
    }
}

bool SQLite::ExcuteNonQuery(const std::string& sql) {
    sqlite3_stmt *stmt;
    if (SQLITE_OK != sqlite3_prepare_v2(m_db, sql.c_str(), -1, &stmt, nullptr))
        return false;
    sqlite3_step(stmt);
    if (SQLITE_OK != sqlite3_finalize(stmt))
        return false;
    return true;
}

bool SQLite::ExcuteNonQuery(const std::wstring& sql) {
    sqlite3_stmt *stmt;
    if (SQLITE_OK != sqlite3_prepare16_v2(m_db, sql.c_str(), -1, &stmt, nullptr))
        return false;
    sqlite3_step(stmt);
    if (SQLITE_OK != sqlite3_finalize(stmt))
        return false;
    return true;
}

bool SQLite::ExcuteNonQuery(Command* pcmd) {
    if (!pcmd) return false;
    return pcmd->Excute();
}

std::unique_ptr<DataBase::DataReader> SQLite::ExcuteQuery(const std::string& sql) {
    sqlite3_stmt *stmt;
    if (SQLITE_OK != sqlite3_prepare_v2(m_db, sql.c_str(), -1, &stmt, nullptr))
        return nullptr;
    return std::unique_ptr<DataBase::DataReader>(new SQLiteDataReader(stmt));
}

std::unique_ptr<DataBase::DataReader> SQLite::ExcuteQuery(const std::wstring& sql) {
    sqlite3_stmt *stmt;
    if (SQLITE_OK != sqlite3_prepare16_v2(m_db, sql.c_str(), -1, &stmt, nullptr))
        return nullptr;
    return std::unique_ptr<DataBase::DataReader>(new SQLiteDataReader(stmt));
}

bool SQLite::ExcuteQuery(const std::string& sql, QueryCallback callback) {
    char *errmsg = nullptr;
    if (SQLITE_OK != sqlite3_exec(m_db, sql.c_str(), callback, nullptr, &errmsg))
        return false;
    return true;
}

bool SQLite::ExcuteQuery(const std::wstring& sql, QueryCallback callback) {
    char *errmsg = nullptr;
    std::string utf8sql = WcharToUtf8(sql);
    if (SQLITE_OK != sqlite3_exec(m_db, utf8sql.c_str(), callback, nullptr, &errmsg))
        return false;
    return true;
}

bool SQLite::BeginTransaction() {
    char *errmsg = nullptr;
    if (SQLITE_OK != sqlite3_exec(m_db, "BEGIN TRANSACTION;", nullptr, nullptr, &errmsg))
        return false;
    return true;
}

bool SQLite::CommitTransaction() {
    char *errmsg = nullptr;
    if (SQLITE_OK != sqlite3_exec(m_db, "COMMIT TRANSACTION;", nullptr, nullptr, &errmsg))
        return false;
    return true;
}

bool SQLite::RollbackTransaction() {
    char *errmsg = nullptr;
    if (SQLITE_OK != sqlite3_exec(m_db, "ROLLBACK TRANSACTION;", nullptr, nullptr, &errmsg))
        return false;
    return true;
}

std::string SQLite::GetLastErrMsgA() {
    std::string msg = sqlite3_errmsg(m_db);
    return std::move(msg);
}

std::wstring SQLite::GetLastErrMsgW() {
    std::wstring msg = (wchar_t*)sqlite3_errmsg16(m_db);
    return std::move(msg);
}

#if defined(UNICODE) || defined(_UNICODE)
std::wstring SQLite::GetLastErrMsg() { return this->GetLastErrMsgW(); }
#else
std::string SQLite::GetLastErrMsg() { return this->GetLastErrMsgA(); }
#endif

//------------------------------------//
SQLite::SQLiteDataReader::SQLiteDataReader(sqlite3_stmt *stmt) : m_stmt(stmt) {}

SQLite::SQLiteDataReader::~SQLiteDataReader() {
    this->Close();
}

bool SQLite::SQLiteDataReader::Read() {
    if (!m_stmt) return false;
    if (SQLITE_ROW != sqlite3_step(m_stmt))
        return false;
    return true;
}

void SQLite::SQLiteDataReader::Close() {
    if (m_stmt) {
        sqlite3_finalize(m_stmt);
        m_stmt = nullptr;
    }
}

int SQLite::SQLiteDataReader::Count() {
    return sqlite3_column_count(m_stmt);
}

std::string SQLite::SQLiteDataReader::NameA(int col) {
    std::string name = sqlite3_column_name(m_stmt, col);
    return std::move(name);
}

std::wstring SQLite::SQLiteDataReader::NameW(int col) {
    std::wstring name = (wchar_t*)sqlite3_column_name16(m_stmt, col);
    return std::move(name);
}

int SQLite::SQLiteDataReader::Type(int col) {
    return sqlite3_column_type(m_stmt, col);
}

std::string SQLite::SQLiteDataReader::GetAString(int col) {
    std::string name = (char*)sqlite3_column_text(m_stmt, col);
    return std::move(name);
}

std::wstring SQLite::SQLiteDataReader::GetWString(int col) {
    std::wstring name = (wchar_t*)sqlite3_column_text16(m_stmt, col);
    return std::move(name);
}

#if defined(UNICODE) || defined(_UNICODE)
std::wstring SQLite::SQLiteDataReader::Name(int col) { return this->NameW(col); }
std::wstring SQLite::SQLiteDataReader::GetString(int col) { return this->GetWString(col); }
#else
std::string SQLite::SQLiteDataReader::Name(int col) { return this->NameA(col); }
std::string SQLite::SQLiteDataReader::GetString(int col) { return this->GetAString(); }
#endif

int SQLite::SQLiteDataReader::GetInt(int col) {
    return sqlite3_column_int(m_stmt, col);
}

long SQLite::SQLiteDataReader::GetInt64(int col) {
    return (long)sqlite3_column_int64(m_stmt, col);
}

double SQLite::SQLiteDataReader::GetFloat(int col) {
    return sqlite3_column_double(m_stmt, col);
}

SQLite::DataBlob SQLite::SQLiteDataReader::GetBlob(int col) {
    DataBlob blob;
    blob.len = sqlite3_column_bytes(m_stmt, col);
    blob.data = (void*)sqlite3_column_blob(m_stmt, col);
    return blob;
}

//------------------------------------//
SQLite::SQLiteCommand::SQLiteCommand(SQLite* sqlite)
        : m_sqlite(sqlite)
        , m_stmt(nullptr) {}

SQLite::SQLiteCommand::SQLiteCommand(SQLite* sqlite, const std::string& sql)
        : m_sqlite(sqlite)
        , m_stmt(nullptr) {
    this->SetCommand(sql);
}

SQLite::SQLiteCommand::SQLiteCommand(SQLite* sqlite, const std::wstring& sql)
        : m_sqlite(sqlite)
        , m_stmt(nullptr) {
    this->SetCommand(sql);
}

SQLite::SQLiteCommand::~SQLiteCommand() {
    this->Clear();
}

bool SQLite::SQLiteCommand::SetCommand(const std::string& sql) {
    if (SQLITE_OK != sqlite3_prepare_v2(m_sqlite->m_db, sql.c_str(), -1, &m_stmt, nullptr))
        return false;
    return true;
}

bool SQLite::SQLiteCommand::SetCommand(const std::wstring& sql) {
    if (SQLITE_OK != sqlite3_prepare16_v2(m_sqlite->m_db, sql.c_str(), -1, &m_stmt, nullptr))
        return false;
    return true;
}

bool SQLite::SQLiteCommand::BindParam(int idx, const std::string& value) {
    if (SQLITE_OK != sqlite3_bind_text(m_stmt, idx, value.c_str(), -1, SQLITE_TRANSIENT))
        return false;
    return true;
}

bool SQLite::SQLiteCommand::BindParam(int idx, const std::wstring& value) {
    if (SQLITE_OK != sqlite3_bind_text16(m_stmt, idx, value.c_str(), -1, SQLITE_TRANSIENT))
        return false;
    return true;
}

bool SQLite::SQLiteCommand::BindParam(int idx, const int value) {
    if (SQLITE_OK != sqlite3_bind_int(m_stmt, idx, value))
        return false;
    return true;
}

bool SQLite::SQLiteCommand::BindParam(int idx, const long value) {
    if (SQLITE_OK != sqlite3_bind_int64(m_stmt, idx, (sqlite_int64)value))
        return false;
    return true;
}

bool SQLite::SQLiteCommand::BindParam(int idx, const double value) {
    if (SQLITE_OK != sqlite3_bind_double(m_stmt, idx, value))
        return false;
    return true;
}

bool SQLite::SQLiteCommand::BindParam(int idx, const SQLite::DataBlob* blob) {
    if (!blob) return false;
    if (SQLITE_OK != sqlite3_bind_blob(m_stmt, idx, blob->data, blob->len, nullptr))
        return false;
    return true;
}

bool SQLite::SQLiteCommand::Excute() {
    sqlite3_step(m_stmt);
    if (SQLITE_OK != sqlite3_reset(m_stmt))
        return false;
    return true;
}

void SQLite::SQLiteCommand::Clear() {
    if (m_stmt) {
        sqlite3_finalize(m_stmt);
        m_stmt = nullptr;
    }
}

//------------------------------------//
std::unique_ptr<DataBase> NewSQLite() {
    return std::unique_ptr<DataBase>(new SQLite);
}

} //end namespace dzlua